

package cn.shengchao.examstar.me.play.ohs.local.appservices;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.shengchao.examstar.me.play.acl.ports.repositories.IUserCoursePlayRepository;
import cn.shengchao.examstar.me.play.domain.*;
import cn.shengchao.examstar.me.play.ohs.local.messages.CoursePlayLogDto;
import cn.shengchao.examstar.me.play.ohs.local.messages.StudyWeekly;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.continew.starter.cache.redisson.util.RedisUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static cn.shengchao.base.constant.Constants.RedisKey.*;

@Slf4j
@Service
public class CoursePlayLogAppService {

    //超时时间
    private final int TIMEOUT = 30;

    @Autowired
    private IUserCoursePlayRepository iUserCoursePlayRepository;

    @PostConstruct
    public void init() {
        new Thread(() -> {
            while (true) {
                List<OnlineUser> onlineUser = RedisUtils.getList(ONLINE_USER_KEY);
                if (onlineUser == null || onlineUser.isEmpty()) {
                    //休眠1分钟
                    sleepOneMinute();
                } else {
                    //过滤重复的学员
                    Collection<Optional<OnlineUser>> optionals = onlineUser.stream()
                        .filter(item -> item.getLastActiveTime() != null && item.getUserId() != null)
                        .collect(Collectors.groupingBy(OnlineUser::getUserId, Collectors.maxBy(Comparator
                            .comparing(OnlineUser::getLastActiveTime))))
                        .values();
                    onlineUser = optionals.stream()
                        .map(item -> item.orElseGet(() -> OnlineUser.builder().build()))
                        .toList();
                    //离线用户
                    Set<OnlineUser> offlineUser = filterOffline(onlineUser);
                    //更新在线用户
                    RedisUtils.delete(ONLINE_USER_KEY);
                    RedisUtils.setList(ONLINE_USER_KEY, onlineUser.stream()
                        .filter(item -> !offlineUser.contains(item))
                        .toList());
                    if (onlineUser.isEmpty() || offlineUser.isEmpty()) {
                        sleepOneMinute();
                    }
                    //处理离线用户
                    handlerOfflineUser(offlineUser);
                }
            }
        }).start();
    }

    private void handlerOfflineUser(Set<OnlineUser> offlineUsers) {
        for (OnlineUser offlineUser : offlineUsers) {
            CompletableFuture.runAsync(() -> handlerPlayProgressLog(offlineUser.getUserId(), handlerPlayLog(String
                .valueOf(offlineUser.getUserId()))));
        }
    }

    //处理看课进度
    private void handlerPlayProgressLog(Long userId, Map<Long, VideoAnalysis.Result> resultMap) {
        if (resultMap.isEmpty()) {
            return;
        }
        Set<Long> knowledgeNodeIds = resultMap.keySet();
        for (Long KnowledgeNodeId : knowledgeNodeIds) {
            String key = COURSE_PROGRESS_LOG_KEY.formatted(userId, KnowledgeNodeId);
            //最后进度
            OnlineUser onlineUser = RedisUtils.get(key);
            if (onlineUser != null) {
                Optional<UserCourse> userCourseOptional = iUserCoursePlayRepository
                    .getUserCourse(userId, KnowledgeNodeId);
                UserCourse userCourse = userCourseOptional.orElse(new UserCourse());
                VideoAnalysis.Result result = resultMap.get(KnowledgeNodeId);
                userCourse.setCourseId(onlineUser.getCourseId());
                userCourse.setKnowledgeNodeId(KnowledgeNodeId);
                userCourse.setStudentId(userId);
                userCourse.setLastPlayTime(onlineUser.getLastActiveTime());
                userCourse.setLastPlayProgress(Math.toIntExact(onlineUser.getProgress()));
                //更新观看时长
                if (userCourse.getWatchLength() != null) {
                    userCourse.setWatchLength(userCourse.getWatchLength() + result.getActualWatchTime());
                } else {
                    userCourse.setWatchLength((long)result.getActualWatchTime());
                }
                //更新播放详情
                List<VideoAnalysis.Interval> intervals = JSON.parseArray(userCourse
                    .getProgressInfo(), VideoAnalysis.Interval.class);
                if (intervals == null) {
                    intervals = Lists.newArrayList();
                }
                //获取上次保存的观看区间，合并新的区间
                intervals.addAll(result.getIntervals());
                intervals = VideoAnalysis.mergeIntervals(intervals);
                userCourse.setWatchProgress(VideoAnalysis.totalProgress(intervals));
                userCourse.setProgressInfo(JSON.toJSONString(intervals));
                iUserCoursePlayRepository.updateUserCourse(userCourse);
                RedisUtils.delete(key);
            }
        }
    }

    private Map<Long, VideoAnalysis.Result> handlerPlayLog(String userId) {
        List<CoursePlayLogDto> coursePlayLogDtoList = RedisUtils.getList(COURSE_PLAY_LOG_KEY.formatted(userId));
        Map<Long, VideoAnalysis.Result> map = new HashMap<>();
        if (coursePlayLogDtoList != null) {
            //删除缓存
            RedisUtils.delete(COURSE_PLAY_LOG_KEY.formatted(userId));
            //按知识点分组
            Map<Long, List<CoursePlayLogDto>> knowledgeGroup = coursePlayLogDtoList.stream()
                .collect(Collectors.groupingBy(CoursePlayLogDto::getKnowledgeNodeId));
            knowledgeGroup.forEach((KnowledgeNodeId, coursePlayLogDto) -> {
                UserCoursePlayLog userCoursePlayLog = new UserCoursePlayLog();
                userCoursePlayLog.setCourseId(coursePlayLogDto.getFirst().getCourseId());
                userCoursePlayLog.setKnowledgeNodeId(KnowledgeNodeId);
                userCoursePlayLog.setStudentId(Long.parseLong(userId));
                userCoursePlayLog.setPoints(coursePlayLogDto.stream()
                    .map(item -> Point.builder()
                        .point(item.getProgress())
                        .speed((float)item.getSpeed())
                        .time(item.getUploadTime())
                        .build())
                    .toList());
                //分析播放记录
                VideoAnalysis.Result result = VideoAnalysis.analyze(userCoursePlayLog.getPoints());
                map.put(KnowledgeNodeId, result);
                userCoursePlayLog.setActualWatchTime(result.getActualWatchTime());
                userCoursePlayLog.setWatchProgress(Math.toIntExact(result.getTotalProgress()));
                iUserCoursePlayRepository.saveUserCoursePlayLog(userCoursePlayLog);
            });
            return map;
        }
        return new HashMap<>();
    }

    private void sleepOneMinute() {
        try {
            Thread.sleep(60 * 1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private Set<OnlineUser> filterOffline(List<OnlineUser> onlineUser) {
        return onlineUser.stream()
            .filter(item -> item.getLastActiveTime().plusSeconds(TIMEOUT).isBefore(LocalDateTime.now()))
            .collect(Collectors.toSet());
    }

    public void saveCoursePlayLog(CoursePlayLogDto coursePlayLogDto, Long userId) {
        CompletableFuture.runAsync(() -> {
            coursePlayLogDto.setUploadTime(LocalDateTime.now());
            //保存在线用户
            RedisUtils.setList(ONLINE_USER_KEY, Lists.newArrayList(OnlineUser.builder()
                .userId(userId)
                .lastActiveTime(LocalDateTime.now())
                .build()));
            //用户播放记录
            RedisUtils.setList(COURSE_PLAY_LOG_KEY.formatted(userId), Lists.newArrayList(coursePlayLogDto));
            //用户课程进度记录
            RedisUtils.set(COURSE_PROGRESS_LOG_KEY.formatted(userId, coursePlayLogDto.getKnowledgeNodeId()), OnlineUser
                .builder()
                .userId(userId)
                .courseId(coursePlayLogDto.getCourseId())
                .lastActiveTime(LocalDateTime.now())
                .progress(coursePlayLogDto.getProgress())
                .build());
        });
    }

    public StudyWeekly studyWeekly(Long userId) {
        DateTime beginWeek = DateUtil.beginOfWeek(new Date());
        List<UserCoursePlayLog> userCoursePlayLog = iUserCoursePlayRepository
            .getUserCoursePlayLog(userId, beginWeek, DateUtil.endOfWeek(new Date()));
        StudyWeekly studyWeekly = new StudyWeekly();
        //课程数量
        long courseCount = userCoursePlayLog.stream().map(UserCoursePlayLog::getCourseId).distinct().count();
        studyWeekly.setCourseCount(courseCount);
        //看课时长
        long playTime = userCoursePlayLog.stream().mapToLong(UserCoursePlayLog::getActualWatchTime).sum();
        studyWeekly.setPlayTime(playTime);
        if (playTime > 0) {
            //平均时长
            studyWeekly.setAveragePlayTime(playTime / DateUtil.dayOfWeek(new Date()) - 1);
        }
        //按天分组求和
        Map<Integer, Long> dayPlayTimeMap = userCoursePlayLog.stream()
            .collect(Collectors.groupingBy(item -> LocalDateTimeUtil.dayOfWeek(item.getBeginTime().toLocalDate())
                .getValue(), Collectors.summingLong(UserCoursePlayLog::getActualWatchTime)));
        Long[] week = new Long[] {0L, 0L, 0L, 0L, 0L, 0L, 0L};
        for (Map.Entry<Integer, Long> entry : dayPlayTimeMap.entrySet()) {
            if (entry.getKey() == 0) {
                week[6] = entry.getValue();
            } else {
                week[entry.getKey() - 2] = entry.getValue();
            }
        }
        studyWeekly.setDailyPlayTime(List.of(week));
        return studyWeekly;
    }
}
